<?php
namespace App\Helper;

use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Redis\Redis;

/**
 * Class IPBlackListHelper
 * @package App\Helper
 * @Bean("IPBlackList")
 */
class IPBlackListHelper
{

    /**
     * IP段最大容错数  超过该数量则IP段限制
     * @var int
     */
    private $MaxIPLen = 3;

    /**
     * 允许 IP 出现的最大异常次数
     * @var int
     */
    private $MaxNormal = 3;

    /**
     * 强制限制黑名单列表
     * @var string
     */
    private $blackListName = 'IPBlackList';

    /**
     * ip解析：
     *
     * ip：解析后的IP
     * mark：解析后的子网掩码
     * ipStart：IP段开始
     * ipEnd：IP段结束
     * @param string $ipStr
     * @return array
     */
    public function ipParse(string $ipStr) {
        $mark_len = 32;
        if (strpos($ipStr, "/") > 0) {
            list($ipStr, $mark_len) = explode("/", $ipStr);
        }
        $ip = ip2long($ipStr);
        $mark = 0xFFFFFFFF << (32 - $mark_len) & 0xFFFFFFFF;
        $ipStart = $ip & $mark;
//        $ipEnd = $ip | (~$mark) & 0xFFFFFFFF; // 因为暂时没有用到IP终结点 所以暂时注释掉
        return array($ip, $ipStart);
    }

    /**
     * 检测是否在黑名单
     * @param string $IP
     * @return bool
     */
    public function inBlack(string $IP){

        // 如果没有IP信息则拦截
        if (empty($IP)) {
            return true;
        }

        // 格式化IP信息
        [$IP, $IPStart] = $this->ipParse($IP);
        $IP = long2ip($IP);
        $IPStart = long2ip($IPStart);

        // 是否被强制封禁
        if (Redis::exists($this->blackListName)) {
            // 查看IP段是否封禁
            $IPSVal = Redis::hGet($this->blackListName, $IPStart);
            if ( !empty($IPSVal) && ($IPSVal >= 1)){
                return true;
            }
            // 查看IP是否封禁
            $IPVal = Redis::hGet($this->blackListName, $IP);
            if ( !empty($IPVal) && ($IPVal >= 1)){
                return true;
            }
        }

        //检查黑名单
        if (Redis::exists($IPStart)) {
            // Redis 中存在IP段 看下是否够长度了  如果超过三条（默认） 则为IP段限制
            if (Redis::hLen($IPStart) >= $this->MaxIPLen) {
               // IP段被限制
                return true;
            }

            // 看下是否 IP 异常次数
            $normal = Redis::hGet($IPStart, $IP);
            if ( !empty($normal) && ($normal >= $this->MaxNormal)){
                return true;
            }
        }

        // 没有IP段限制记录
        return false;
    }

}
